using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using DiggThis.Forms;
using DiggThis.Properties;
using WindowsLive.Writer.Api;
using System.Text;
using WLWPluginBase.Win32;

namespace DiggThis
{
    [WriterPlugin("B3E14793-948F-49af-A347-D19C374A7C4F", "DiggThis", "Resources.DiggThis.png",
        PublisherUrl = "http://lvildosola.blogspot.com",
        Description = "Inserts a Digg This button in the Windows Live Writer editor.")]
    [InsertableContentSource("DiggThis")]
    public class DiggThisPlugin : SmartContentSource
    {
        #region Constants
        private const RegexOptions RXO_DEFAULTS = (RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
        private const string RXP_EXTRACT_POST_TITLE = @"<span[\s]+class=postTitle.*?>(?<postTitle>.*?)</span>";
        private const string RXP_EXTRACT_POST_BODY = @"<div[\s]+class=postBody.*?>(?<postBody>.*?)</div>";
        private const string RXP_EXTRACT_POST_TEXT = @"<p[\s]*.*?>(?<postText>.*?)</p>";
        private const string RXP_STRIP_HTML = @"<[a-zA-Z\/][^>]*>";
        private const string RXG_POST_TITLE = "postTitle";
        private const string RXG_POST_BODY = "postBody";
        private const string RXG_POST_TEXT = "postText";
        private const string WNDCLSNAME_IE_SERVER = "Internet Explorer_Server";
        #endregion Constants

        #region Overrides
        /// <summary>
        /// Overriden to always allow content insertion.
        /// </summary>
        /// <param name="owner">Handle of the parent window.</param>
        /// <param name="newContent">Reference to the smart content instance.</param>
        /// <returns>Always returns <see cref="DialogResult.OK"/> to indicate it's okay to insert contents.</returns>
        public override DialogResult CreateContent(IWin32Window owner, ISmartContent newContent)
        {
            // Get access to the plugin properties.
            DiggThisConfig settings = DiggThisConfig.Load(newContent);

            // Extract the post title.
            settings.Title = ExtractPostTitle(owner.Handle);
            // Extract the post's first paragraph.
            settings.Text = ExtractPostText(owner.Handle);

            // We simply want to go ahead and accept the content creation. The assignment of the smart
            // content will be done later.
            return DialogResult.OK;
        }
        /// <summary>
        /// Returns a <see cref="SmartContentEditor"/> instance to allow editing of plugin properties.
        /// </summary>
        /// <param name="editorSite">Connection between a <see cref="SmartContentEditor"/> object and its user interface context.</param>
        /// <returns></returns>
        public override SmartContentEditor CreateEditor(ISmartContentEditorSite editorSite)
        {
            // Create a new instance of the smart content editor.
            return (new DiggThisEditor(editorSite));
        }
        /// <summary>
        /// Generates the HTML code that will be used when displaying within the Windows Live Writer editor only.
        /// </summary>
        /// <param name="content">Reference to the smart content object.</param>
        /// <param name="ctx">Context used for publishing.</param>
        /// <returns>HTML code to display in the Windows Live Writer editor.</returns>
        public override string GenerateEditorHtml(ISmartContent content, IPublishingContext ctx)
        {
            // Get access to the plugin properties.
            DiggThisConfig settings = DiggThisConfig.GetInstance(content);

            // Only perform the initialization if the content has yet to be initialized or it is different.
            if (!content.Files.Contains(settings.Compact ? settings.ImgCompactFilename : settings.ImgFilename))
            {
                // Prepare the base name of the image file.
                settings.ImgFilename = string.Format("{0}.png", Guid.NewGuid());

                // Retrieve handles to the embedded resources representing the 2 supported layout formats.
                IntPtr hbm = Resources.DiggThisEditor.GetHbitmap();
                IntPtr hbmCompact = Resources.DiggThisCompactEditor.GetHbitmap();

                // Create the necessary local files to represent the 2 supported layout formats.
                content.Files.RemoveAll();
                content.Files.AddImage(settings.ImgFilename, Image.FromHbitmap(hbm), ImageFormat.Png);
                content.Files.AddImage(settings.ImgCompactFilename, Image.FromHbitmap(hbmCompact), ImageFormat.Png);
            }

            // Return the HTML code to render the image.
            return string.Format("<img style=\"background-color: {0}; border: 0px;\" src=\"file:///{1}\"></img>",
                settings.BkgColor, settings.Compact ? settings.ImgCompactUrl : settings.ImgUrl);
        }
        /// <summary>
        /// Generates the HTML code that will be used in the published post.
        /// </summary>
        /// <param name="content">Reference to the smart content object.</param>
        /// <param name="ctx">Context used for publishing.</param>
        /// <returns>HTML code to display in the Windows Live Writer editor.</returns>
        public override string GeneratePublishHtml(ISmartContent content, IPublishingContext ctx)
        {
            // Get access to the plugin properties.
            DiggThisConfig settings = DiggThisConfig.GetInstance(content);

            // Prepare the place holder for the HTML to be generated.
            StringBuilder sb = new StringBuilder();

            // Deteremine if we have any optional properties to render.
            if (settings.HasOptionalProperties)
            {
                sb.AppendLine();
                sb.AppendLine("<script type='text/javascript'><!--");
                AppendFormatLineIfNotNullOrEmpty(sb, "digg_url = '{0}';", settings.Url);
                AppendFormatLineIfNotNullOrEmpty(sb, "digg_title = '{0}';", settings.Title);
                AppendFormatLineIfNotNullOrEmpty(sb, "digg_bodytext = '{0}';", settings.Text);
                AppendFormatLineIfNotNullOrEmpty(sb, "digg_topic = '{0}';", settings.Topic);
                AppendFormatLineIfNotNullOrEmpty(sb, "digg_bgcolor = '{0}';", settings.BkgColor);
                AppendFormatLineIfNotNullOrEmpty(sb, "digg_skin = '{0}';", settings.Skin);
                sb.AppendLine("//--></script>");
            }
            // Add the line that renders the actual Digg This button.
            sb.AppendLine("<script src='http://digg.com/tools/diggthis.js' type='text/javascript'></script>");

            // Return the resulting HTML.
            return sb.ToString();
        }
        #endregion Overrides

        #region Helpers
        /// <summary>
        /// Extracts the post title to represent the title to pass in the Digg submission.
        /// </summary>
        /// <param name="owner">The handle to the parent window.</param>
        /// <returns>The extracted post title if successful. Otherwise an empty string.</returns>
        private static string ExtractPostTitle(IntPtr owner)
        {
            // Prepare the return value.
            string postTitle = string.Empty;

            // Get access to the window item that represents the web browser in the Windows Live Writer editor.
            Win32EnumWindowsItem item = Win32EnumWindows.FindByClassName(owner, WNDCLSNAME_IE_SERVER);
            // Determine if it is visible (i.e. active at the time of the request).
            if (item != null)
            {
                // TODO: Add support for Beta 1 - DIV instead of SPAN in RXP_EXTRACT_POST_TITLE.
                // Prepare the expression that will help us isolate the post title.
                Regex rx = new Regex(RXP_EXTRACT_POST_TITLE, RXO_DEFAULTS);
                // Extract the post title and assign it to the respective property.
                postTitle = rx.Match(Win32IEHelper.GetHtml(item.Handle)).Groups[RXG_POST_TITLE].Value;
            }

            // Return the resulting post tile, but only if it does not match the default post title
            // used to indicate a post title has not been specified.
            return (Resources.BlankPostTitle.Equals(postTitle) ? string.Empty : postTitle);
        }
        /// <summary>
        /// Extracts the post body.
        /// </summary>
        /// <param name="owner">The handle to the parent window.</param>
        /// <returns>The extracted post body if successful. Otherwise an empty string.</returns>
        private static string ExtractPostBody(IntPtr owner)
        {
            // Prepare the return value.
            string postBody = string.Empty;

            // Get access to the window item that represents the web browser in the Windows Live Writer editor.
            Win32EnumWindowsItem item = Win32EnumWindows.FindByClassName(owner, WNDCLSNAME_IE_SERVER);
            // Determine if it is visible (i.e. active at the time of the request).
            if (item != null)
            {
                // Prepare the expression that will help us isolate the post title.
                Regex rx = new Regex(RXP_EXTRACT_POST_BODY, RXO_DEFAULTS | RegexOptions.Singleline | RegexOptions.Multiline);
                // Extract the post title and assign it to the respective property.
                postBody = rx.Match(Win32IEHelper.GetHtml(item.Handle)).Groups[RXG_POST_BODY].Value;
            }

            // Return the resulting post tile, but only if it does not match the default post title
            // used to indicate a post title has not been specified.
            return postBody;
        }
        /// <summary>
        /// Extracts the post's first paragraph to represent the abstract to pass in the Digg submission.
        /// </summary>
        /// <param name="owner">The handle to the parent window.</param>
        /// <returns>The extracted post text if successful. Otherwise an empty string.</returns>
        private static string ExtractPostText(IntPtr owner)
        {
            // Prepare the return value.
            string postText = string.Empty;

            // Get the contents from the IE window.
            string postBody = ExtractPostBody(owner);
            // Prepare the expression that will help us isolate the first paragraph in the post
            // that we will use as the text.
            Regex rx = new Regex(RXP_EXTRACT_POST_TEXT, RXO_DEFAULTS);
            // Because we may have more than one result. We need to make sure we get to the first
            // paragraph that has any content.
            MatchCollection matches = rx.Matches(postBody);
            foreach (Match match in matches)
            {
                // Get the value of the match.
                string value = match.Groups[RXG_POST_TEXT].Value;
                // Make sure the capture is not empty.
                if (!string.IsNullOrEmpty(value))
                {
                    // Remove all HTML element tags.
                    postText = new Regex(RXP_STRIP_HTML, RXO_DEFAULTS).Replace(value, string.Empty).Trim();
                    // Nothing further to process.
                    break;
                }
            }
            // If after this processing we have not found anything yet, then assume the post text is
            // the entire contents stripped of any HTML.
            if (string.IsNullOrEmpty(postText))
            {
                // This condition may occur if the user types the first paragraph but does not press
                // the Enter key. WLW does not wrap this text in a <p></p>.
                postText = new Regex(RXP_STRIP_HTML, RXO_DEFAULTS).Replace(postBody, string.Empty).Trim();
            }

            // Return the resulting post first paragraph.
            return postText;
        }
        /// <summary>
        /// Helper method to optionally append a formatted line to the given <see cref="StringBuilder"/> instance.
        /// </summary>
        /// <param name="sb"><see cref="StringBuilder"/> instance to which to append the formatted line.</param>
        /// <param name="format">A string containing a single format specifications.</param>
        /// <param name="value">A string containing the value to replace in the format specification.</param>
        private static void AppendFormatLineIfNotNullOrEmpty(StringBuilder sb, string format, string value)
        {
            // Only append if the given value is valid.
            if (!string.IsNullOrEmpty(value))
            {
                // Append the formatted string and a new line.
                sb.AppendFormat(format, value);
                sb.AppendLine();
            }
        }
        #endregion Helpers
    }
}